#include "./mdb_uart.h"
#include "../xlib/gpio.h"
#include "../xlib/dma.h"
#include "../xlib/uart.h"

namespace driver {

MdbUart mdbUart;


namespace mdb_uart {

using namespace stm32f1;

using TX = gpio::Cfg<gpio::PA<2>, gpio::Mode::OUT_50MHz_AF_PP>;
using RX = gpio::Cfg<gpio::PA<3>, gpio::Mode::INPUT_FLOAT>;
using UART = uart::Uart2;
using DMA_TX = dma::DmaSel<UART::BASE_VAL>::TX_CHAN;
using DMA_RX = dma::DmaSel<UART::BASE_VAL>::RX_CHAN;

} 
using namespace mdb_uart;
using namespace utils;

void MdbUart::init() {
    m_mq.init();
    m_flags.init();
    m_flags.set_flags(FLAG_DMA_TC);

    rcc::enable<TX, RX, UART, DMA_TX, DMA_RX>();
    gpio::config<RX, TX>();
    UART::set_cfg<uart::MdbCfg>();
    DMA_RX::set_cfg<dma::UartMdbRecvCfg, UART::DR_ADDR_VAL>(
        reinterpret_cast<uint32_t>(m_recv_buf), RECV_BUF_SIZE);
    NVIC_EnableIRQ(USART2_IRQn);
    NVIC_EnableIRQ(DMA1_Channel7_IRQn);
}

Error MdbUart::receive_once(MdbResp &resp) {
    if (m_recv_slice.len <= 0) {
        if (m_mq.poll(m_recv_slice, MDB_RESP_MS) != osOK) {
            return ec::MDB_RESP_TIMEOUT;
        }
    }
    uint16_t v;
    uint8_t index = 0;
    uint8_t s = 0;
    for (;;) {
        if (!take(v)) {
            return ec::MDB_TIMEOUT;
        }
        if (index >= RECV_BUF_SIZE) {
            return ec::MDB_INVALID;
        }
        m_byte_recv_buf[index] = v;
        index += 1;
        if (v & 0x100) {
            break;
        }
        s += v & 0xFF;
    }
    resp.buf = m_byte_recv_buf;
    resp.len = index;

    if (index == 1) {
        if (m_byte_recv_buf[0] == MdbResp::ACK) {
            return ec::OK;
        }
        return ec::MDB_INVALID;
    }
    if (s != m_byte_recv_buf[index - 1]) {
        return ec::MDB_CHECK_SUM_FAIL;
    }
    write_no_check(MdbResp::ACK);
    return ec::OK;
}

void MdbUart::write_by_dma(const void *buf, uint16_t len) {
    DMA_TX::set_cfg<dma::UartMdbWriteCfg, UART::DR_ADDR_VAL>(
        reinterpret_cast<uint32_t>(buf), len);
}

void MdbUart::on_recv_idle() {
    uint32_t index = RECV_BUF_SIZE - DMA_RX::ndtr();
    if (index == m_recv_index) {
        return;
    }
    using namespace utils;
    if (index > m_recv_index) {
        Uint16View bw(m_recv_buf + m_recv_index, index - m_recv_index);
        m_mq.post(bw);
    } else {
        Uint16View bw(m_recv_buf + m_recv_index, RECV_BUF_SIZE - m_recv_index);
        m_mq.post(bw);
        if (index != 0) {
            bw.buf = m_recv_buf;
            bw.len = index;
            m_mq.post(bw);
        }
    }
    m_recv_index = index;
}


}

using namespace driver;

extern "C" void USART2_IRQHandler() {
    if (UART::is_idle()) {
        UART::clear_idle_flag();
        mdbUart.on_recv_idle();
    }
}

extern "C" void DMA1_Channel7_IRQHandler() {
    if (DMA_TX::is_tc()) {
        DMA_TX::clear_flags();
        mdbUart.on_dma_tc();
    }
}


